// DOM操作  =>  获取元素,操作元素(修改元素的内容 属性 样式 绑定事件),元素的增删改查

// 目的: 对DOM操作进行封装,可以方便用户快捷获取元素,操作元素,以及元素的增删改查

// 构造函数版  => 获取元素,返回获取的元素组成的集合(伪数组 -> 实例化对象)
function GetEle(selector) { // selector 传入css选择器(字符串)   原生dom元素(单元素)  原生dom元素的集合(多元素) => 转化为对应的实例化对象

    if (typeof selector == "string") {
        // 传入的参数 css选择器(字符串) 
        var list = document.querySelectorAll(selector);  // 对应选择器获取元素 (伪数组)
        list = Array.from(list);  // 伪数组转真数组
    } else if (selector instanceof HTMLElement) {  // 传入的参数是html元素
        // 传入的参数  原生dom元素(单元素) => li 
        var list = [selector];  // 不管是单元素还是多元素统一放到集合中处理
    } else if (typeof selector == "object" && "length" in selector) {
        // 传入的参数是集合 (伪数组/真数组)   dom元素的集合(多元素) => [li,li,li,li]
        var list = Array.from(selector);
    } else {
        var err = new TypeError("请传入指定类型的参数");
        throw err;
    }


    Object.setPrototypeOf(list, GetEle.prototype); // 直接修改伪数组的[[Prototype]]可能会丢失length属性 => 先转真数组,真数组改伪数组

    // 自定义构造函数的返回值
    return list; // 返回伪数组, 有数组的结构,但是又可以访问GetEle.prototype上的方法
}

// 根据伪数组(实例化对象) => 操作集合中的元素

// text()  获取和设置元素内的文本结构
// text("123123")   有参数就设置
// text()           没有参数就获取
GetEle.prototype.text = function (con) { // 设置元素内文本结构
    // this -> 调用此方法的实例化对象 ((实例化对象可以继续访问GetEle.prototype上的方法 ))

    // arguments => 函数的内置参数,用于存本次函数调用时实际参数的集合

    if (arguments.length >= 1) {  // 有参数
        // 设置
        // for (var i = 0; i < this.length; i++) {
        //     var ele = this[i];
        //     ele.innerText = con;
        // }

        this.each((ele, index) => {  // 遍历实例化对象(伪数组)  => 取出每个元素和下标
            ele.innerText = con;
        })
        return $(this);  // 返回实例化对象(新的)

    } else { // 没有参数
        // 获取
        return this[0].innerText;
    }
}

// html()  获取和设置元素内的html结构
// html("123123")   有参数就设置
// html()           没有参数就获取
GetEle.prototype.html = function (con) { // 设置元素内文本结构
    // this -> 调用此方法的实例化对象

    // arguments => 函数的内置参数,用于存本次函数调用时实际参数的集合

    if (arguments.length >= 1) {  // 有参数
        // 设置
        // for (var i = 0; i < this.length; i++) {
        //     var ele = this[i];
        //     ele.innerHTML = con;
        // }

        this.each((ele, index) => {  // 遍历实例化对象(伪数组)  => 取出每个元素和下标
            ele.innerHTML = con;
        })

        // return this;  // 返回实例化对象本身(原本的  => 伪数组)
        return $(this);  // 返回实例化对象(新的)

    } else { // 没有参数
        // 获取
        return this[0].innerHTML;
    }
}


// val()  获取和设置元素内的value值
// val("123123")   有参数就设置
// val()           没有参数就获取
GetEle.prototype.val = function (con) { // 设置元素内文本结构
    // this -> 调用此方法的实例化对象

    // arguments => 函数的内置参数,用于存本次函数调用时实际参数的集合

    if (arguments.length >= 1) {  // 有参数
        // 设置
        // for (var i = 0; i < this.length; i++) {
        //     var ele = this[i];
        //     ele.value = con;
        // }

        this.each((ele, index) => {  // 遍历实例化对象(伪数组)  => 取出每个元素和下标
            ele.value = con;
        })

        return $(this);  // 返回实例化对象(新的)

    } else { // 没有参数
        // 获取
        return this[0].value;
    }
}


// hide()  隐藏元素
// show()  显示元素

GetEle.prototype.hide = function () {
    // this -> 调用此方法的实例化对象
    // for (var i = 0; i < this.length; i++) {
    //     var ele = this[i];
    //     ele.style.display = "none";
    // }

    this.each((ele, index) => {  // 遍历实例化对象(伪数组)  => 取出每个元素和下标
        ele.style.display = "none";
    })
    return $(this);  // 返回实例化对象(新的)
}

GetEle.prototype.show = function () {
    // this -> 调用此方法的实例化对象
    // for (var i = 0; i < this.length; i++) {
    //     var ele = this[i];
    //     ele.style.display = "block";
    // }
    this.each((ele, index) => {  // 遍历实例化对象(伪数组)  => 取出每个元素和下标
        ele.style.display = "block";
    })

    return $(this);  // 返回实例化对象(新的)
}



GetEle.prototype.on = function (type, callback) {
    // this -> 调用此方法的实例化对象
    // for (var i = 0; i < this.length; i++) {
    //     var ele = this[i];
    //     ele.addEventListener(type, callback);
    // }
    this.each((ele, index) => {  // 遍历实例化对象(伪数组)  => 取出每个元素和下标
        ele.addEventListener(type, callback);
    })
    return $(this);  // 返回实例化对象(新的)
}

// 对应事件类型  对on方法进行二次封装
GetEle.prototype.click = function (callback) {
    // this -> 调用此方法的实例化对象 (实例化对象可以继续访问GetEle.prototype上的方法 )
    this.on("click", callback);
    return $(this);  // 返回实例化对象(新的)
}

GetEle.prototype.dblclick = function (callback) {
    // this -> 调用此方法的实例化对象 (实例化对象可以继续访问GetEle.prototype上的方法 )
    this.on("dblclick", callback);
    return $(this);  // 返回实例化对象(新的)
}

GetEle.prototype.mousedown = function (callback) {
    // this -> 调用此方法的实例化对象 (实例化对象可以继续访问GetEle.prototype上的方法 )
    this.on("mousedown", callback);
    return $(this);  // 返回实例化对象(新的)
}

// ....



GetEle.prototype.off = function (type, callback) {
    // this -> 调用此方法的实例化对象
    // for (var i = 0; i < this.length; i++) {
    //     var ele = this[i];
    //     ele.removeEventListener(type, callback);
    // }
    this.each((ele, index) => {  // 遍历实例化对象(伪数组)  => 取出每个元素和下标
        ele.removeEventListener(type, callback);
    })
    return $(this);  // 返回实例化对象(新的)
}

GetEle.prototype.each = function (callback) {
    // this -> 调用此方法的实例化对象 (伪数组)
    for (var i = 0; i < this.length; i++) {
        callback(this[i], i, this);
    }
    return $(this);  // 返回实例化对象(新的)
}

// css()   获取和设置元素的css样式
// css("backgrounc-color","red");  两个参数就是设置
// css("backgrounc-color");       一个参数就是获取
GetEle.prototype.css = function (cssKey, cssVal) {
    // this -> 调用此方法的实例化对象 (伪数组)
    if (arguments.length >= 2) { // 两个参数就是设置
        this.each((ele) => {
            ele.style[cssKey] = cssVal;
        })
        return $(this);  // 返回实例化对象(新的)
    } else if (arguments.length == 1) { // 一个参数
        // a. 如果参数是字符串 => css("backgrounc-color") 就是获取 (默认返回集合中的第一个)
        // b. 如果参数是对象类型 => {width:"200px",height:"200px"}  遍历对象(一个参数),取出键值对在设置

        if (typeof cssKey == "string") {
            // return this[0].style[cssKey];   // style只能获取行内样式 
            var ele = this[0];
            try {
                return window.getComputedStyle(ele)[cssKey];
            } catch {
                return ele.currentStyle[cssKey];
            }
        } else if (typeof cssKey == "object") {
            for (var key in cssKey) {
                var val = cssKey[key];

                // this.each((ele) => {
                //     ele.style[key] = val;
                // })
                this.css(key, val);
            }
            return $(this);  // 返回实例化对象(新的)
        }


    } else {
        var err = new Error("Place enter at last one parameter!") // 请传入至少一个参数
        throw err;
    }
}

// width()   获取和设置元素的宽度
// width("100px")   有一个参数就设置
// width()          没有参数就获取(100px => 去单位 => 100)
GetEle.prototype.width = function (val) {
    // this -> 调用此方法的实例化对象 (伪数组)

    // 取值时记得去单位  赋值时记得加单位
    if (arguments.length >= 1) {

        // 关于单位处理
        // (1) 如果有单位, 直接设置  => 100 px em rem pt % vmin vmax vw vh
        // (2) 如果没有单位, 默认px 
        var unitReg = /(px|em|rem|%|pt|vmin|vmax|vw|vh)$/;
        if (unitReg.test(val)) {
            this.css("width", val);
        } else {
            this.css("width", val + "px");
        }

        return $(this);  // 返回实例化对象(新的)
    } else {
        return parseInt(this.css("width"));
    }
}

GetEle.prototype.height = function (val) {
    // this -> 调用此方法的实例化对象 (伪数组)
    if (arguments.length >= 1) {

        // 关于单位处理
        // (1) 如果有单位, 直接设置  => 100 px em rem pt % vmin vmax vw vh
        // (2) 如果没有单位, 默认px 
        var unitReg = /(px|em|rem|%|pt|vmin|vmax|vw|vh)$/;
        if (unitReg.test(val)) {
            this.css("height", val);
        } else {
            this.css("height", val + "px");
        }

        return $(this);  // 返回实例化对象(新的)
    } else {
        return parseInt(this.css("height"));
    }
}



// attr()   获取和设置元素的html属性(标签内可见的 class id title data-xxx)
// attr("class","one");  两个参数就是设置  等价于ele.setAttribute("class","one")
// attr("class");       一个参数就是获取  等价于ele.getAttribute("class","one")
GetEle.prototype.attr = function (key, val) {
    // this -> 调用此方法的实例化对象 (伪数组)

    if (arguments.length >= 2) {
        this.each((ele, index) => {
            ele.setAttribute(key, val);
        })
        return $(this);  // 返回实例化对象(新的)
    } else if (arguments.length == 1) {
        return this[0].getAttribute(key);
    } else {
        var err = new Error("Place enter at last one parameter!") // 请传入至少一个参数
        throw err;
    }
}


// prop()   获取和设置元素节点的内置属性(className id title dataset  clientWidth offsetLeft )
// prop("className","one");  两个参数就是设置  等价于ele.className = "one"
// prop("className");       一个参数就是获取  等价于ele.className
GetEle.prototype.prop = function (key, val) {
    // this -> 调用此方法的实例化对象 (伪数组)
    if (arguments.length >= 2) {
        this.each((ele, index) => {
            ele[key] = val;
        })
        return $(this);  // 返回实例化对象(新的)
    } else if (arguments.length == 1) {
        return this[0][key];
    } else {
        var err = new Error("Place enter at last one parameter!") // 请传入至少一个参数
        throw err;
    }

}

GetEle.prototype.addClass = function (clsStr) {  // clsStr => 传入class名的字符串 "one"  "one two three"
    var list = clsStr.split(/\s+/);
    this.each((ele) => {
        ele.classList.add(...list); //多个class名之间用逗号分隔
    })
    return $(this);  // 返回实例化对象(新的)
}

GetEle.prototype.removeClass = function (clsStr) {  // clsStr => 传入class名的字符串 "one"  "one  two three"
    var list = clsStr.split(/\s+/);
    this.each((ele) => {
        console.log(ele.classList, list);
        ele.classList.remove(...list); //多个class名之间用逗号分隔
    })
    return $(this);  // 返回实例化对象(新的)
}


GetEle.prototype.hasClass = function (className) {  // clsStr => 传入class名的字符串 "one"  "one two three"
    var flag = false;  // 假设没有
    this.each((ele) => {
        if (ele.classList.contains(className)) {  // 有一个元素有 就变为true
            flag = true;
        }
    })
    return flag;
}


// $(".list li").css({ backgroundColor: "red" });   // .list li都变红
// $(".list li").eq(1).css({ backgroundColor: "red" }); // .list li中下标为1的li变红

// eq(index)  返回对应下标的元素(形成的实例化对象)
GetEle.prototype.eq = function (index) {
    // this -> 调用此方法的实例化对象 (伪数组)
    // return this[index];   // 此写法返回原生DOM元素 => 不能使用GetEle.prototype对象的方法

    return $(this[index]); // 传入原生dom元素 将其转化为实例化对象 (伪数组), 能使用GetEle.prototype对象的方法
}

// $(".list li").eq(1).siblings().css({ backgroundColor: "red" }); // .list li中除下标为1以外的同级li变红

// siblings(index)  返回除特定元素以外的同级元素(形成的实例化对象)
GetEle.prototype.siblings = function (index) {
    // this -> 调用此方法的实例化对象 (伪数组)
    // return this[index];   // 此写法返回原生DOM元素 => 不能使用GetEle.prototype对象的方法

    var ele = this[0]; // 默认获取集合中的第一个元素 (取出特定元素)
    var parentEle = ele.parentElement; //找到目标元素的父元素
    var children = parentEle.children; //父元素中所有的子元素 (包含自身)


    var list = Array.from(children).filter(item => item !== ele); // 筛选出除自身以外的同级元素

    return $(list);  // 将其转化为实例化对象
}







function $(selector) {  // 调用$()方法 => 快速获取元素,返回元素的集合(实例化对象)
    return new GetEle(selector);
}
